package org.unit.testmgmt.generator;

import java.util.List;
import java.util.Vector;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.Annotation;
import org.unit.testmgmt.base.IUTMConstants;
import org.unit.testmgmt.preferences.UTMPreferences;
import org.unit.testmgmt.tml.Assertion;
import org.unit.testmgmt.tml.AssertionType;
import org.unit.testmgmt.tml.Constructor;
import org.unit.testmgmt.tml.Method;
import org.unit.testmgmt.tml.Mocks;
import org.unit.testmgmt.tml.Param;
import org.unit.testmgmt.tml.Result;
import org.unit.testmgmt.tml.Settings;
import org.unit.testmgmt.tml.Test;
import org.unit.testmgmt.tml.TestBase;
import org.unit.testmgmt.tml.TestBases;
import org.unit.testmgmt.tml.TestCase;
import org.unit.testmgmt.tml.Testprio;
import org.unit.testmgmt.tml.UTMElements.UTMClassesAndPackages;
import org.unit.testmgmt.tml.UTMModel;

/**
 * The default test-class java generator. On the base of the TML the test-class will be generated.
 * 
 * @author Robert Streng
 * 
 */
@SuppressWarnings("restriction")
// TODO avoid restrictions
public class TestClassGenerator implements ITestClassGenerator, IUTMConstants {

	private static final String VERSION = "org.unit.testmgmt-1.0.0";

	protected static final String ANNO_GENERATED_NAME = "Generated";
	protected static final String ANNO_GENERATED = "@" + ANNO_GENERATED_NAME;

	protected static final String ANNO_TESTPRIO_NAME = "Testprio";
	protected static final String ANNO_TESTPRIO = "@" + ANNO_TESTPRIO_NAME;

	protected static final String ANNO_GENERATED_HOOK_NAME = "GeneratedHook";
	protected static final String ANNO_GENERATED_HOOK = "@" + ANNO_GENERATED_HOOK_NAME;

	protected static final String ANNO_JUNIT_TEST = "@Test";
	protected static final String ANNO_JUNIT_BEFORE = "@Before";
	protected static final String ANNO_JUNIT_BEFORE_CLASS = "@BeforeClass";
	protected static final String ANNO_JUNIT_AFTER = "@After";
	protected static final String ANNO_JUNIT_AFTER_CLASS = "@AfterClass";

	protected final static String FAIL_ASSERTION = "Assert.fail();";

	protected final static String TESTBASE_METHOD_PREFIX = "createTestBase";

	protected final static String STANDARD_METHOD_BEFORE = "setUp";
	protected final static String STANDARD_METHOD_BEFORE_ClASS = "setUpBeforeClass";
	protected final static String STANDARD_METHOD_AFTER = "tearDown";
	protected final static String STANDARD_METHOD_AFTER_CLASS = "tearDownAfterClass";

	protected final static String EXCEPTION = "Exception";

	protected final static String RETURN = "\n";
	protected final static String QUOTES = "\"";

	protected String annoGenerated = null;

	protected String testmethodPrefix;

	protected String testmethodPostfix;

	protected boolean defaultTestbaseMethodCreated = false;

	protected Vector<String> changedHookMethodNames;

	@Override
	public IFile generate(UTMModel model, IProgressMonitor monitor) throws Exception {

		changedHookMethodNames = new Vector<String>();
		defaultTestbaseMethodCreated = false;

		Test tmlTest = model.getTmlTest();
		Settings tmlSettings = tmlTest.getSettings();

		UTMClassesAndPackages utmClassesAndPackages = model.getUtmElements()
				.getClassesAndPackages();
		ICompilationUnit testClass = utmClassesAndPackages.getTestClass();
		String testClassName = utmClassesAndPackages.getTestClassName();
		String baseClassName = utmClassesAndPackages.getBaseClassName();
		IType type;

		// begin task
		int methodSize = tmlTest.getMethod().size();
		int increment;

		if (methodSize >= 300) {
			increment = 50;
		}
		else if (methodSize >= 100) {
			increment = 30;
		}
		else {
			increment = 20;
		}

		methodSize = methodSize / increment;

		monitor.beginTask("", 6 + methodSize);

		// create or update test-class-frame
		type = createTestClassFrame(testClass, tmlTest, testClassName);

		// increment task
		if (incrementTask(monitor))
			return null;

		// delete generated elements
		if (testClass.exists()) {
			deleteGeneratedElements(testClass, tmlSettings);
		}

		// increment task
		if (incrementTask(monitor))
			return null;

		// create standard-imports
		createStandardImports(testClass, tmlTest);

		// increment task
		if (incrementTask(monitor))
			return null;

		// create standard-class-fields
		createStandardClassFields(type, tmlTest, testClassName);

		// increment task
		if (incrementTask(monitor))
			return null;

		// create standard-methods
		createStandardMethods(type, tmlSettings);

		// increment task
		if (incrementTask(monitor))
			return null;

		// create test-base-context
		createTestBaseMethods(type, tmlTest, baseClassName);

		// increment task
		if (incrementTask(monitor))
			return null;

		// create test-methods
		if (createTestMethods(type, tmlTest, baseClassName, monitor, increment)) {
			return null;
		}

		// make source beautiful
		String src = GeneratorUtils.organizeImports(testClass);
		src = GeneratorUtils.formatSource(src);

		// save source
		IPackageFragment testPackage = model.getUtmElements().getClassesAndPackages()
				.getTestPackage();
		testClass = testPackage.createCompilationUnit(testClass.getElementName(), src, true, null);

		return (IFile) testClass.getResource();
	}

	/**
	 * Creates the standard methods.
	 * 
	 * @param type
	 * @param tmlSettings
	 * @throws JavaModelException
	 */
	protected void createStandardMethods(IType type, Settings tmlSettings)
			throws JavaModelException {
		if (tmlSettings == null) {
			return;
		}

		if (tmlSettings.isSetUp()) {
			GeneratorUtils.createMethod(type, MOD_PUBLIC, TYPE_VOID, STANDARD_METHOD_BEFORE,
					EXCEPTION, null, "", false, createAnnoGenerated(), ANNO_JUNIT_BEFORE);
		}

		if (tmlSettings.isSetUpBeforeClass()) {
			GeneratorUtils.createMethod(type, MOD_PUBLIC + MOD_STATIC_WITH_BLANK, TYPE_VOID,
					STANDARD_METHOD_BEFORE_ClASS, EXCEPTION, null, "", false,
					createAnnoGenerated(), ANNO_JUNIT_BEFORE_CLASS);
		}

		if (tmlSettings.isTearDown()) {
			GeneratorUtils.createMethod(type, MOD_PUBLIC, TYPE_VOID, STANDARD_METHOD_AFTER,
					EXCEPTION, null, "", false, createAnnoGenerated(), ANNO_JUNIT_AFTER);
		}

		if (tmlSettings.isTearDownBeforeClass()) {
			GeneratorUtils.createMethod(type, MOD_PUBLIC + MOD_STATIC_WITH_BLANK, TYPE_VOID,
					STANDARD_METHOD_AFTER_CLASS, "Exception", null, "", false,
					createAnnoGenerated(), ANNO_JUNIT_AFTER_CLASS);
		}
	}

	/**
	 * Create the hook for the test base (before).
	 * 
	 * @param type
	 * @param testBaseMethodName
	 * @throws JavaModelException
	 */
	protected void createHookTestBaseBefore(IType type, String testBaseMethodName)
			throws JavaModelException {

		String hookMethodName = testBaseMethodName + "HookBefore";
		if (!changedHookMethodNames.contains(hookMethodName)) {
			changedHookMethodNames.add(hookMethodName);

			GeneratorUtils.createMethod(type, MOD_PRIVATE, TYPE_VOID, hookMethodName, "Exception",
					"Object[] paramList", "", false, ANNO_GENERATED_HOOK);
		}
	}
	
	/**
	 * Create the hook for the test base (after).
	 * 
	 * @param type
	 * @param testBaseMethodName
	 * @param testBaseName
	 * @throws JavaModelException
	 */
	protected void createHookTestBaseAfter(IType type, String testBaseMethodName,
			String testBaseName) throws JavaModelException {
		String testBaseVariableName = GeneratorUtils.firstCharToLower(testBaseName);
		String hookMethodName = testBaseMethodName + "HookAfter";
		if (!changedHookMethodNames.contains(hookMethodName)) {
			changedHookMethodNames.add(hookMethodName);

			GeneratorUtils.createMethod(type, MOD_PRIVATE, testBaseName, hookMethodName,
					"Exception", testBaseName + " " + testBaseVariableName, "return "
							+ testBaseVariableName + ";", false, ANNO_GENERATED_HOOK);
		}
		;
	}

	/**
	 * Create a hook after a method call.
	 *  
	 * @param type
	 * @param hookMethodName
	 * @param param
	 * @throws JavaModelException
	 */
	protected void createHookAfterMethodCall(IType type, String hookMethodName, String param)
			throws JavaModelException {
		GeneratorUtils.createMethod(type, MOD_PRIVATE, TYPE_VOID, hookMethodName, "Exception",
				param, "", false, ANNO_GENERATED_HOOK);
	}

	/**
	 * Increments the task.
	 * 
	 * @param monitor
	 * @return true if not canceled 
	 */
	protected boolean incrementTask(IProgressMonitor monitor) {
		return incrementTask(monitor, 1);
	}

	/**
	 * Increments the task.
	 * 
	 * @param monitor
	 * @param i
	 * @return true if not canceled 
	 */
	protected boolean incrementTask(IProgressMonitor monitor, int i) {
		if (monitor.isCanceled())
			return true;
		monitor.worked(i);
		return false;
	}

	/**
	 * Deletes the generated elements. 
	 * 
	 * @param testClass
	 * @param tmlSettings
	 * @throws JavaModelException
	 */
	protected void deleteGeneratedElements(ICompilationUnit testClass, Settings tmlSettings)
			throws JavaModelException {
		IType[] types = testClass.getTypes();
		IMethod method;
		IField field;

		for (IType type : types) {
			for (IJavaElement element : type.getChildren()) {
				if (element instanceof IMethod) {
					method = (IMethod) element;

					if (!deleteStandardMethod(method.getElementName().replace(".java", ""),
							tmlSettings)) {
						continue;
					}

					if (isGenerated(method.getAnnotations())) {
						method.delete(true, null);
					}
					else if (isGeneratedHook(method.getAnnotations())) {
						if (!isHookMethodChanged(method)) {
							method.delete(true, null);
						}
						else {
							changedHookMethodNames.add(method.getElementName());
						}
					}
				}
				else if (element instanceof IField) {
					field = (IField) element;
					if (isGenerated(field.getAnnotations())) {
						field.delete(true, null);
					}
				}
			}
		}
	}

	/**
	 * Returns if the hook method is changed.
	 * 
	 * @param method
	 * @return true is the hook method changed
	 * @throws JavaModelException
	 */
	protected boolean isHookMethodChanged(IMethod method) throws JavaModelException {
		String source = method.getSource();
		int indexOfMethodBody = source.indexOf("{") + 1;
		String methodBody = source.substring(indexOfMethodBody).replace(RETURN, "")
				.replace("\t", "");

		if (methodBody.startsWith("return")) {
			return false;
		}
		else if (methodBody.startsWith("}")) {
			return false;
		}

		return true;
	}

	/**
	 * Returns if the annotation for the generated hooks is set.
	 * 
	 * @param annotations
	 * @return true if the annotation for the generated hooks is set.
	 */
	protected boolean isGeneratedHook(IAnnotation[] annotations) {
		for (IAnnotation annotation : annotations) {
			if (ANNO_GENERATED_HOOK_NAME.equals(annotation.getElementName()))
				return true;
		}

		return false;
	}

	/**
	 * @param tmlSettings
	 * @param replace
	 * @return isStandardMethod
	 */
	protected boolean deleteStandardMethod(String methodName, Settings tmlSettings) {
		if (STANDARD_METHOD_BEFORE.equals(methodName)) {
			if (!tmlSettings.isSetUp()) {
				return true;
			}
			else {
				return false;
			}
		}
		else if (STANDARD_METHOD_BEFORE_ClASS.equals(methodName)) {
			if (!tmlSettings.isSetUpBeforeClass()) {
				return true;
			}
			else {
				return false;
			}
		}
		else if (STANDARD_METHOD_AFTER.equals(methodName)) {
			if (!tmlSettings.isTearDown()) {
				return true;
			}
			else {
				return false;
			}
		}
		else if (STANDARD_METHOD_AFTER_CLASS.equals(methodName)) {
			if (!tmlSettings.isTearDownBeforeClass()) {
				return true;
			}
			else {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns if the annotation generated is set.
	 * 
	 * @param annotations
	 * @return true if the annotation for the generated hooks is set.
	 */	
	protected boolean isGenerated(IAnnotation[] annotations) {
		for (IAnnotation annotation : annotations) {
			if (ANNO_GENERATED_NAME.equals(annotation.getElementName()))
				return true;
		}

		return false;
	}
	
	/**
	 * Creates the test class frame.
	 * 
	 * @param testCompilationUnit
	 * @param tmlTest
	 * @param testClassName
	 * @return the created test class frame
	 * @throws JavaModelException
	 */
	protected IType createTestClassFrame(ICompilationUnit testCompilationUnit, Test tmlTest,
			String testClassName) throws JavaModelException {
		IType type = testCompilationUnit.getType(testClassName);

		if (!type.exists()) {
			return createTestClassFrame(testCompilationUnit, tmlTest, testClassName, null);
		}
		else {
			// check if recreation of test-class-frame is necessary
			Vector<Annotation> annotationsToDelete = getAnnotationsToDelete(type, tmlTest);

			if (annotationsToDelete != null) {
				for (Annotation annotation : annotationsToDelete)
					annotation.delete(true, null);

				String source = type.getSource();
				type.delete(true, null);
				return createTestClassFrame(testCompilationUnit, tmlTest, testClassName, source);
			}
		}

		return type;
	}

	/**
	 * Returns the annotation to delete.
	 * @param type
	 * @param tmlTest
	 * @return
	 * @throws JavaModelException
	 */
	protected Vector<Annotation> getAnnotationsToDelete(IType type, Test tmlTest)
			throws JavaModelException {
		Vector<Annotation> annotationsToDelete = new Vector<Annotation>();
		Annotation annotation;
		boolean recreationNecessary = false;

		for (IAnnotation iAnnotation : type.getAnnotations()) {
			if (iAnnotation instanceof Annotation) {
				annotation = (Annotation) iAnnotation;

				if (ANNO_GENERATED_NAME.equals(iAnnotation.getElementName())) {
					annotationsToDelete.add(annotation);
					IMemberValuePair[] memberValuePairs = annotation.getMemberValuePairs();
					for (IMemberValuePair valuePair : memberValuePairs) {
						if (!VERSION.equals(valuePair.getValue())) {
							recreationNecessary = true;
							break;
						}
					}
				}
				else if (ANNO_TESTPRIO_NAME.equals(iAnnotation.getElementName())) {
					annotationsToDelete.add(annotation);
					IMemberValuePair[] memberValuePairs = annotation.getMemberValuePairs();

					if (memberValuePairs.length == 0) {
						if (tmlTest.getTestPrio().compareTo(Testprio.DEFAULT) != 0)
							recreationNecessary = true;
					}

					for (IMemberValuePair valuePair : memberValuePairs) {
						if (!valuePair.getValue().toString()
								.endsWith(tmlTest.getTestPrio().toString())) {
							recreationNecessary = true;
							break;
						}
					}
				}
			}
		}

		if (!recreationNecessary)
			return null;

		return annotationsToDelete;
	}

	/**
	 * Creates a test class frame. 
	 * 
	 * @param testCompilationUnit
	 * @param tmlTest
	 * @param testclassName
	 * @param source
	 * @return the created test class frame
	 * @throws JavaModelException
	 */
	protected IType createTestClassFrame(ICompilationUnit testCompilationUnit, Test tmlTest,
			String testclassName, String source) throws JavaModelException {
		// create annotations
		String annotations = createTestClassFrameAnnotations(tmlTest.getTestPrio());

		// create type
		if (source == null) {
			String customComment = getTestClassComment();
			
			String superType = UTMPreferences.getTestClassSuperType();
			String extendsStmt = "";
			if (!(superType == null || "".equals(superType))) {
				extendsStmt = " extends " + superType;
			}

			source = customComment + annotations.toString() + MOD_PUBLIC + " class " + testclassName + extendsStmt
					+ "{ " + RETURN + "}";
		}
		else {
			source = annotations + source;
		}

		IType type = testCompilationUnit.createType(source, null, true, null);

		return type;
	}
	
	protected String getTestClassComment() {
		return "";
	};

	/**
	 * Creates the test class annotations.
	 * 
	 * @param testprio
	 * @return the created annotations
	 */
	protected String createTestClassFrameAnnotations(Testprio testprio) {
		// create annotations
		StringBuilder annotations = new StringBuilder();

		// create generator-annotation
		annotations.append(createAnnoGenerated());

		// test-priority-annotation
		annotations.append(createAnnoTestprio(testprio));

		return annotations.toString();
	}

	/**
	 * Creates the annotation generated.
	 * 
	 * @return the created annotation
	 */
	protected String createAnnoGenerated() {
		if (annoGenerated == null) {
			annoGenerated = ANNO_GENERATED + "(value=" + QUOTES + VERSION + QUOTES + ")" + RETURN;
		}
		return annoGenerated;
	}

	/**
	 * Creates the standard imports.
	 * 
	 * @param compilationUnit
	 * @param tmlTest
	 * @throws JavaModelException
	 */
	protected void createStandardImports(ICompilationUnit compilationUnit, Test tmlTest)
			throws JavaModelException {
		compilationUnit.createImport("java.util.*", null, null);
		compilationUnit.createImport("org.junit.After", null, null);
		compilationUnit.createImport("org.junit.Assert", null, null);
		compilationUnit.createImport("org.junit.BeforeClass", null, null);
		compilationUnit.createImport("org.junit.Test", null, null);
		compilationUnit.createImport("org.unit.testmgmt.annotations.Testprio", null, null);
		compilationUnit.createImport("org.unit.testmgmt.annotations.GeneratedHook", null, null);

		if (tmlTest.getSettings().isLogger()) {
			compilationUnit.createImport("java.util.logging.Logger", null, null);
		}
	}

	/**
	 * Create standard class fields.
	 * 
	 * @param type
	 * @param tmlTest
	 * @param testclassName
	 * @throws JavaModelException
	 */
	protected void createStandardClassFields(IType type, Test tmlTest, String testclassName)
			throws JavaModelException {
		if (tmlTest.getSettings().isLogger()) {
			String logger = createAnnoGenerated() + " " + MOD_PRIVATE
					+ " Logger logger = Logger.getLogger(" + testclassName + ".class.toString());";
			type.createField(logger, null, false, null);
		}
	}
	
	/**
	 * Creates test base methods.
	 * 
	 * @param type
	 * @param tmlTest
	 * @param testBaseName
	 * @throws JavaModelException
	 */
	protected void createTestBaseMethods(IType type, Test tmlTest, String testBaseName)
			throws JavaModelException {
		Settings tmlSettings = tmlTest.getSettings();
		TestBases tmlTestbases = tmlTest.getTestBases();
		if (tmlSettings == null || tmlTestbases == null) {
			return;
		}

		String testBaseMethodBody;
		String testBaseMethodName;

		for (Constructor tmlConstructor : tmlTestbases.getConstructor()) {

			boolean createHookTestBaseBefore = tmlSettings.isHookCreateTestBaseBefore()
					&& tmlConstructor.getParam().size() > 0;

			for (TestBase tmlTestbase : tmlConstructor.getTestBase()) {
				testBaseMethodName = createTestBaseMethodName(tmlTestbase.getName());

				testBaseMethodBody = createTestBaseMethodBody(tmlTestbase, testBaseName,
						testBaseMethodName, tmlConstructor.getParam(), tmlSettings);

				GeneratorUtils.createMethod(type, MOD_PRIVATE, testBaseName, testBaseMethodName,
						"Exception", null, testBaseMethodBody, false, createAnnoGenerated());

				if (createHookTestBaseBefore) {
					createHookTestBaseBefore(type, testBaseMethodName);
				}
				if (tmlSettings.isHookCreateTestBaseAfter()) {
					createHookTestBaseAfter(type, testBaseMethodName, testBaseName);
				}
			}

			if (tmlConstructor.getTestBase().size() == 0) {
				createTestBaseMethodDefault(type, testBaseName, tmlConstructor.getParam());
			}
		}
	}

	/**
	 * Creates the test base method with default values.
	 * 
	 * @param type
	 * @param testbaseName
	 * @param params
	 * @throws JavaModelException
	 */
	protected void createTestBaseMethodDefault(IType type, String testbaseName, List<Param> params)
			throws JavaModelException {
		if (defaultTestbaseMethodCreated)
			return;

		String paramValueList;
		if (params != null) {
			paramValueList = createParamValueList(params, null);
		}
		else {
			paramValueList = "";
		}

		StringBuilder sbMethodBody = new StringBuilder();
		sbMethodBody.append("return new ").append(testbaseName).append("(").append(paramValueList)
				.append(");");

		GeneratorUtils.createMethod(type, MOD_PRIVATE, testbaseName, TESTBASE_METHOD_PREFIX, null,
				null, sbMethodBody.toString(), createAnnoGenerated());

		defaultTestbaseMethodCreated = true;
	}

	/**
	 * Creates the test base method name.
	 * 
	 * @param tmlTestBaseName
	 * @return test base method name
	 */
	protected String createTestBaseMethodName(String tmlTestBaseName) {
		String testBaseName = GeneratorUtils.firstCharToUpper(tmlTestBaseName);
		String testBaseMethodName = TESTBASE_METHOD_PREFIX + testBaseName;
		return testBaseMethodName;
	}

	/**
	 * Creates the test base method body.
	 * 
	 * @param tmlTestbase
	 * @param testBaseName
	 * @param testBaseMethodName
	 * @param params
	 * @param tmlSettings
	 * @return the created test base method body
	 */
	protected String createTestBaseMethodBody(TestBase tmlTestbase, String testBaseName,
			String testBaseMethodName, List<Param> params, Settings tmlSettings) {

		StringBuilder sbMethodBody = new StringBuilder();

		String constructorParams = "";
		if (params.size() > 0) {
			if (tmlSettings.isHookCreateTestBaseBefore()) {
				// parameter array
				String paramArray = createParamArray(params, tmlTestbase.getParamValue());

				sbMethodBody.append(paramArray).append(RETURN).append(testBaseMethodName)
						.append("HookBefore(paramList);");

				constructorParams = createParamArrayList(params);
			}
			else {
				constructorParams = createParamValueList(params, tmlTestbase.getParamValue());
			}
		}

		String testBaseMocks = createTestBaseMocks(tmlTestbase.getMocks());
		String testBaseVariableName = GeneratorUtils.firstCharToLower(testBaseName);

		// test-base initialization
		sbMethodBody.append(testBaseName).append(" ").append(testBaseVariableName).append("=")
				.append("new ").append(testBaseName).append("(").append(constructorParams)
				.append(") {").append(testBaseMocks).append("};").append(RETURN);

		if (tmlSettings.isHookCreateTestBaseAfter()) {
			sbMethodBody.append(testBaseVariableName).append("=").append(testBaseMethodName)
					.append("HookAfter(").append(testBaseVariableName).append(");").append(RETURN);
		}

		// return
		sbMethodBody.append("return ").append(testBaseVariableName).append(";");

		return sbMethodBody.toString();
	}

	/**
	 * Creates a parameter array list.
	 * 
	 * @param params
	 * @return parameter array list
	 */
	protected String createParamArrayList(List<Param> params) {
		StringBuilder sbParamArrayList = new StringBuilder();

		boolean firstInit = true;

		for (int i = 0; i < params.size(); i++) {
			if (!firstInit) {
				sbParamArrayList.append(",");
			}
			else {
				firstInit = false;
			}

			sbParamArrayList.append("(").append(params.get(i).getType()).append(")paramList[")
					.append(i).append("]");
		}

		return sbParamArrayList.toString();
	}

	/**
	 * Creates a parameter array list.
	 * 
	 * @param params
	 * @param paramValues
	 * @return parameter array list
	 */
	protected String createParamArray(List<Param> params, List<String> paramValues) {
		StringBuilder sbParamArray = new StringBuilder();

		boolean firstInit = true;

		for (int i = 0; i < params.size() && i < paramValues.size(); i++) {
			if (firstInit) {
				sbParamArray.append("Object[] paramList = new Object[").append(params.size())
						.append("];");
				firstInit = false;
			}
			sbParamArray.append("paramList[").append(i).append("] = ")
					.append(formatValue(paramValues.get(i), params.get(i).getType())).append(";")
					.append(RETURN);
		}

		return sbParamArray.toString();
	}

	/**
	 * Creates the test base mocks.
	 * 
	 * @param mocks
	 * @return test base mocks
	 */
	protected String createTestBaseMocks(Mocks mocks) {
		if (mocks == null)
			return "";

		StringBuilder sbMockMethods = new StringBuilder();
		String resultType;
		String resultValue;
		String modifier;

		for (Method tmlMockMethod : mocks.getMethod()) {
			if (tmlMockMethod.getResult() != null) {
				resultType = tmlMockMethod.getResult().getType();
				resultValue = tmlMockMethod.getResult().getValue();
				resultValue = formatValue(resultValue, resultType);
				resultValue = "return " + resultValue + ";";
			}
			else {
				resultType = TYPE_VOID;
				resultValue = "";
			}

			modifier = tmlMockMethod.getModifier();
			if (MOD_PACKAGE.equals(modifier)) {
				modifier = "";
			}

			sbMockMethods.append(modifier).append(" ").append(resultType).append(" ")
					.append(tmlMockMethod.getName()).append("(")
					.append(createParamList(tmlMockMethod.getParam())).append(") {")
					.append(resultValue).append("}");
		}

		return sbMockMethods.toString();
	}

	/**
	 * Creates a initializer value.
	 * 
	 * @param type
	 * @return initializer value
	 */
	protected String createInitValue(String type) {
		String value;

		if (isString(type) || isChar(type)) {
			value = "";
		}
		else if (isBoolean(type)) {
			value = "false";
		}
		else if (isNumber(type)) {
			if (isDouble(type)) {
				value = "0.0";
			}
			else {
				value = "0";
			}
		}
		else {
			value = "null";
		}

		return value;
	}

	/**
	 * Returns if the type is String.
	 * 
	 * @param type
	 * @return true if type is String
	 */
	protected boolean isString(String type) {
		if (type.startsWith(TYPE_STRING)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is char.
	 * 
	 * @param type
	 * @return true if type is char
	 */
	protected boolean isChar(String type) {
		if (type.startsWith(TYPE_CHAR)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is int.
	 * 
	 * @param type
	 * @return true if type is int
	 */
	protected boolean isInt(String type) {
		if (type.startsWith(TYPE_INT)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is Integer.
	 * 
	 * @param type
	 * @return true if type is Integer
	 */

	protected boolean isInteger(String type) {
		if (type.startsWith(TYPE_INTEGER)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is double.
	 * 
	 * @param type
	 * @return true if type is double
	 */
	protected boolean isDouble(String type) {
		if (type.startsWith(TYPE_DOUBLE)) {
			return true;
		}

		return false;
	}
	
	/**
	 * Returns if the type is float.
	 * 
	 * @param type
	 * @return true if type is float
	 */
	protected boolean isFloat(String type) {
		if (type.startsWith(TYPE_FLOAT)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is boolean.
	 * 
	 * @param type
	 * @return true if type is boolean
	 */
	protected boolean isBoolean(String type) {
		if (type.startsWith(TYPE_BOOLEAN)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is a number.
	 * 
	 * @param type
	 * @return true if type is a number
	 */
	protected boolean isNumber(String type) {
		if (isInt(type) || isInteger(type) || isDouble(type) || isFloat(type)) {
			return true;
		}

		return false;
	}

	/**
	 * Returns if the type is a array.
	 * 
	 * @param type
	 * @return true if type is a array
	 */
	protected boolean isArray(String type) {
		if (type.endsWith(TYPE_ARRAY)) {
			return true;
		}

		return false;
	}

	/**
	 * Creates the test methods.
	 * 
	 * @param type
	 * @param tmlTest
	 * @param baseClassName
	 * @param monitor
	 * @param increment
	 * @return true if the processing was successful
	 * @throws JavaModelException
	 */
	protected boolean createTestMethods(IType type, Test tmlTest, String baseClassName,
			IProgressMonitor monitor, int increment) throws JavaModelException {

		int i = 0;

		boolean failAssertions = tmlTest.getSettings().isFailAssertions();
		boolean hookAfterMethodCall = tmlTest.getSettings().isHookAfterMethodCall();

		for (Method tmlMethod : tmlTest.getMethod()) {
			createTestMethod(type, tmlMethod, baseClassName, failAssertions, hookAfterMethodCall);

			if (i++ == increment) {
				i = 0;
				// increment task
				if (incrementTask(monitor))
					return true;
			}
		}

		return false;
	}

	/**
	 * Creates a test method.
	 * 
	 * @param type
	 * @param tmlMethod
	 * @param baseClassName
	 * @param failAssertions
	 * @param hookAfterMethodCall
	 * @throws JavaModelException
	 */
	protected void createTestMethod(IType type, Method tmlMethod, String baseClassName,
			boolean failAssertions, boolean hookAfterMethodCall) throws JavaModelException {
		String testMethodNamePrefix = getTestmethodPrefix();
		String testMethodNamePostfix = getTestmethodPostfix();
		String testMethodName;
		String testMethodBody;

		// create test-method-name
		if (testMethodNamePrefix != null && testMethodNamePrefix.length() > 0) {
			testMethodName = testMethodNamePrefix
					+ GeneratorUtils.firstCharToUpper(tmlMethod.getName()) + testMethodNamePostfix;
		}
		else {
			testMethodName = tmlMethod.getName() + testMethodNamePostfix;
		}

		// create test-method-body
		testMethodBody = createTestMethodBody(type, tmlMethod, testMethodName, baseClassName,
				failAssertions, hookAfterMethodCall);

		GeneratorUtils.createMethod(type, MOD_PUBLIC, TYPE_VOID, testMethodName, "Exception", null,
				testMethodBody, true, createAnnoGenerated(), ANNO_JUNIT_TEST);
	}

	/**
	 * Creates the test method body.
	 * 
	 * @param type
	 * @param tmlMethod
	 * @param methodName
	 * @param baseClassName
	 * @param failAssertions
	 * @param hookAfterMethodCall
	 * @return the created test method body
	 * @throws JavaModelException
	 */
	protected String createTestMethodBody(IType type, Method tmlMethod, String methodName,
			String baseClassName, boolean failAssertions, boolean hookAfterMethodCall)
			throws JavaModelException {
		StringBuilder sbTestMethodBody = new StringBuilder();
		List<Param> params = tmlMethod.getParam();
		String testbaseMethodName = "";
		String testBaseVariableName = GeneratorUtils.firstCharToLower(baseClassName);

		// test-base-variable
		if (!tmlMethod.isStatic()) {
			sbTestMethodBody.append(baseClassName).append(" ").append(testBaseVariableName)
					.append(";");
		}

		// result-variable
		Result result = tmlMethod.getResult();
		String resultVariableName = "";
		String resultType = "";
		if (result != null) {
			resultVariableName = result.getName();
			resultType = result.getType();

			sbTestMethodBody.append(resultType).append(" ").append(resultVariableName).append(";");
		}

		List<TestCase> testCases = tmlMethod.getTestCase();
		boolean isPublic = MOD_PUBLIC.equals(tmlMethod.getModifier());
		boolean hookGenerated;
		hookGenerated = false;

		for (TestCase tmlTestcase : testCases) {
			sbTestMethodBody.append(RETURN + RETURN + "// ").append(tmlTestcase.getName())
					.append(RETURN);

			testbaseMethodName = createTestBaseMethodName(tmlTestcase.getTestBase());

			createTestCaseBody(sbTestMethodBody, tmlMethod.getName(), baseClassName,
					testBaseVariableName, testbaseMethodName, resultVariableName, resultType,
					params, tmlTestcase.getParamValue(), isPublic, tmlMethod.isStatic());

			// assertions
			createAssertionsMethodBody(sbTestMethodBody, resultVariableName, resultType,
					testBaseVariableName, tmlTestcase);

			// hook
			if (hookAfterMethodCall) {
				String hookMethodName = methodName + "HookAfterMethodCall";
				String resultVar = "";
				String param = baseClassName + " " + testBaseVariableName;
				if (result != null) {
					param += ", " + resultType + " " + resultVariableName;
					resultVar = ", " + resultVariableName;
				}

				sbTestMethodBody.append(hookMethodName).append("(").append(testBaseVariableName)
						.append(resultVar).append(");");

				if (!hookGenerated && !changedHookMethodNames.contains(hookMethodName)) {
					hookGenerated = true;
					createHookAfterMethodCall(type, hookMethodName, param);
				}
			}
		}

		// fail-assertion
		if (failAssertions) {
			sbTestMethodBody.append(RETURN + RETURN).append(FAIL_ASSERTION);
		}

		return sbTestMethodBody.toString();
	}

	/**
	 * Creates the test method body.
	 * 
	 * @param sbTestMethodBody
	 * @param methodName
	 * @param baseClassName
	 * @param testBaseVariableName
	 * @param testBaseMethodName
	 * @param resultVariableName
	 * @param resultType
	 * @param params
	 * @param paramValues
	 * @param isPublic
	 * @param isStatic
	 */
	protected void createTestCaseBody(StringBuilder sbTestMethodBody, String methodName,
			String baseClassName, String testBaseVariableName, String testBaseMethodName,
			String resultVariableName, String resultType, List<Param> params,
			List<String> paramValues, boolean isPublic, boolean isStatic) {

		String baseName;

		// create test-base
		if (!isStatic) {
			baseName = testBaseVariableName;
			sbTestMethodBody.append(testBaseVariableName).append("=").append(testBaseMethodName)
					.append("();");
		}
		else {
			baseName = baseClassName;
		}

		// result
		if (resultVariableName.length() > 0) {
			sbTestMethodBody.append(resultVariableName).append("=");
		}

		String paramValueList = createParamValueList(params, paramValues);
		if (isPublic) {
			// method-call
			sbTestMethodBody.append(baseName).append(".").append(methodName).append("(")
					.append(createParamValueList(params, paramValues)).append(");");
		}
		else {

			// method-call with white-box-call
			if (paramValueList.length() > 0) {
				paramValueList = ", new Object[]{" + paramValueList + "}";
			}

			if (isStatic) {
				baseName += ".class";
			}

			sbTestMethodBody.append("Whitebox.invokeMethod(").append(baseName).append(", ")
					.append(QUOTES).append(methodName).append(QUOTES).append(paramValueList)
					.append(");");
		}
	}

	/**
	 * Creates the method body for the assertions.
	 * 
	 * @param sbTestMethodBody
	 * @param resultVariableName
	 * @param resultType
	 * @param testBaseVariableName
	 * @param tmlTestCase
	 */
	protected void createAssertionsMethodBody(StringBuilder sbTestMethodBody,
			String resultVariableName, String resultType, String testBaseVariableName,
			TestCase tmlTestCase) {

		String baseType;

		for (Assertion tmlAssertion : tmlTestCase.getAssertion()) {
			// base
			String base;
			if ("{result}".equals(tmlAssertion.getBase())) {
				if ("".equals(resultVariableName)) {
					continue;
				}

				base = "result";
				baseType = resultType;
			}
			else {
				base = testBaseVariableName + "." + tmlAssertion.getBase() + "()";
				baseType = tmlAssertion.getBaseType();
			}

			// assertion-type
			AssertionType type = tmlAssertion.getType();
			String assertionType = createAssertionType(type, baseType);

			// Assertion
			sbTestMethodBody.append(RETURN + "Assert.").append(assertionType).append("(");

			// message
			String message = "";
			if (tmlAssertion.getMessage().length() > 0) {
				message = tmlTestCase.getName() + ": " + tmlAssertion.getMessage();
				sbTestMethodBody.append(QUOTES).append(message).append(QUOTES).append(", ");
			}

			// actual
			if (type == AssertionType.EQUALS || type == AssertionType.NOT_EQUALS) {
				// test-value
				String testValue = tmlAssertion.getValue();
				testValue = formatValue(testValue, baseType);
				sbTestMethodBody.append(testValue).append(", ");

				// expected
				sbTestMethodBody.append(base);

				// delta
				if (isNumber(baseType) && !isArray(baseType)) {
					sbTestMethodBody.append(", 0");
				}
			}
			else {
				// expected
				sbTestMethodBody.append(base);
			}

			sbTestMethodBody.append(");");
		}

	}

	/**
	 * Returns the assertion as String.
	 * 
	 * @param type
	 * @param baseType
	 * @return assertion as String
	 */
	protected String createAssertionType(AssertionType type, String baseType) {
		String assertionType = "assertEquals";

		if (type == AssertionType.EQUALS) {
			if (isArray(baseType)) {
				assertionType = "assertArrayEquals";
			}
			else {
				assertionType = "assertEquals";
			}
		}
		else if (type == AssertionType.NOT_EQUALS) {
			assertionType = "assertNotEquals";
		}
		else if (type == AssertionType.IS_NULL) {
			assertionType = "assertNull";
		}
		else if (type == AssertionType.NOT_NULL) {
			assertionType = "assertNotNull";
		}
		else if (type == AssertionType.IS_TRUE) {
			assertionType = "assertTrue";
		}
		else if (type == AssertionType.IS_FALSE) {
			assertionType = "assertFalse";
		}
		return assertionType;
	}

	/**
	 * Do the type specific formats for the value if length greater than 0 or not null otherwise a
	 * formated initialized-value is created.
	 * 
	 * @param value
	 *            value to format
	 * @param type
	 *            UTM-type (mandatory parameter)
	 * @return formated an if value
	 */
	protected String formatValue(String value, String type) {
		String resultValue = "";

		if (value == null || value.length() == 0) {
			resultValue = createInitValue(type);
			resultValue = decorateValue(resultValue, type);
		}
		else {
			if (isArray(type)) {
				String[] values = value.split(",");
				boolean firstValue = true;

				for (String tmpValue : values) {
					tmpValue = tmpValue.trim();

					if ("".equals(tmpValue)) {
						tmpValue = createInitValue(type);
					}

					tmpValue = decorateValue(tmpValue, type);

					if (firstValue) {
						resultValue = tmpValue;
						firstValue = false;
					}
					else {
						resultValue = resultValue + (", ") + tmpValue;
					}
				}
			}
			else {
				resultValue = decorateValue(value, type);
			}
		}

		if (isArray(type)) {
			resultValue = "new " + type + "{" + resultValue + "}";
		}

		return resultValue;
	}

	/**
	 * Decorates the value depend on the type.
	 * 
	 * @param value
	 * @param type
	 * @return decorated value
	 */
	protected String decorateValue(String value, String type) {
		if (isString(type)) {
			value = QUOTES + value + QUOTES;
		}
		else if (isChar(type)) {
			value = "'" + value + "'";
		}

		return value;
	}

	/**
	 * Creates a parameter list.
	 * 
	 * @param params
	 * @return the created parameter list
	 */
	protected String createParamList(List<Param> params) {
		Param tmlParam;
		StringBuilder sbParamList = new StringBuilder();
		boolean firstInit = true;

		for (int i = 0; i < params.size(); i++) {
			if (!firstInit)
				sbParamList.append(", ");
			else
				firstInit = false;

			tmlParam = params.get(i);

			sbParamList.append(tmlParam.getType()).append(" ").append(tmlParam.getName());
		}

		return sbParamList.toString();
	}

	/**
	 * Creates a parameter list with values.
	 * 
	 * @param params
	 * @param paramValues
	 * @return the created parameter list with values
	 */
	protected String createParamValueList(List<Param> params, List<String> paramValues) {
		StringBuilder sbParamList = new StringBuilder();
		boolean firstInit = true;

		Param tmlParam;
		String value, type;

		for (int i = 0; i < params.size() && (paramValues == null || i < paramValues.size()); i++) {
			if (!firstInit)
				sbParamList.append(", ");
			else
				firstInit = false;

			tmlParam = params.get(i);
			type = tmlParam.getType();

			if (paramValues != null) {
				value = paramValues.get(i);
			}
			else {
				value = "";
			}

			value = formatValue(value, type);

			sbParamList.append(value);
		}
		return sbParamList.toString();
	}

	protected String getTestmethodPrefix() {
		if (testmethodPrefix == null)
			testmethodPrefix = UTMPreferences.getTestMethodPrefix();

		return testmethodPrefix;
	}

	/**
	 * @return the test method post fix
	 */
	protected String getTestmethodPostfix() {
		if (testmethodPostfix == null)
			testmethodPostfix = UTMPreferences.getTestMethodPostfix();

		return testmethodPostfix;
	}
	/**
	 * Creates the annotation test priority.
	 * 
	 * @return the created annotations
	 */
	protected String createAnnoTestprio(Testprio testprio) {
		if (testprio == Testprio.DEFAULT)
			return ANNO_TESTPRIO + RETURN;

		return ANNO_TESTPRIO + "(prio=org.unit.testmgmt.tml.Testprio." + testprio + ")" + RETURN;
	}

}
